import math
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.metrics import accuracy_score

class nn:
    def __init__(self, data, label):
        self.data = data
        self.label = label

    def sigmod(self, x):
        res = 1.0/(1+np.exp(-1*x))
        return res

    def forward(self, x, w, b):
        res = self.sigmod(np.dot(x,w)+b)
        return res

    def train(self, hide_dim, out_dim, label_set, n_max = 2000, yita = 0.01):
        np.random.seed(0)
        # 输入维度
        input_dim = np.shape(self.data)[1]
        # 权重初始化
        W1 = np.random.randn(input_dim,hide_dim) / np.sqrt(input_dim)
        b1 = np.zeros((1,hide_dim))
        W2 = np.random.randn(hide_dim, out_dim) / np.sqrt(hide_dim)
        b2 = np.zeros((1,out_dim))
        n = 0
        while n < n_max:
            # 隐藏层
            a = self.forward(self.data, W1, b1)
            # 输出层
            y = self.forward(a, W2, b2)
            error_out = y * (1-y) * (self.label-y)
            error_hide = a * (1-a) * np.dot(error_out,W2.T)
            W1 = W1 + yita*np.dot(self.data.T,error_hide)
            b1 = b1 + yita*np.sum(error_hide,axis = 0)
            W2 = W2 + yita*np.dot(a.T,error_out)
            b2 = b2 + yita*np.sum(error_out,axis = 0)
            n = n + 1
        y_pred = label_set[np.argmax(y,axis = 1)].reshape(np.shape(self.data)[0],1)
        return y_pred, W1, b1, W2, b2

    def test(self, W1, b1, W2, b2, label_set):
        a = self.forward(self. data, W1, b1)
        y = self.forward(a, W2, b2)
        y_pred = label_set[np.argmax(y,axis = 1)].reshape(np.shape(self.data)[0],1)
        return y_pred


# 构建多分类的网络
# 数据加载、绘制
digits = load_digits()
plt.figure(1)
fig = plt.figure(figsize = (6, 6))
fig.subplots_adjust(left = 0, right = 1, bottom = 0, top = 1, hspace = 0.05, wspace = 0.05)
for i in range(64):
    ax = fig.add_subplot(8, 8, i + 1, xticks = [], yticks=[])
    ax.imshow(digits.images[i], cmap = plt.cm.binary)
    ax.text(0, 7, str(digits.target[i]))
label_set = list(set(digits.target))
n = np.shape(label_set)[0]
t = np.zeros((np.shape(digits.data)[0],n))
for i in range(np.shape(digits.data)[0]):
    for j in range(n):
        if digits.target[i] == label_set[j]:
            t[i,j] = 1
N_train = int(np.shape(digits.data)[0]*0.6)
X_train = digits.data[:N_train,:]
t_train = t[:N_train,:]
X_test = digits.data[N_train:,:]
t_test = t[N_train,:]
train_pred,w1,b1,w2,b2 = nn(X_train,t_train).train(hide_dim=30,out_dim=n,label_set=np.array(label_set))
test_pred = nn(X_test,t_test).test(w1,b1,w2,b2,np.array(label_set))
acc1 = accuracy_score(digits.target[:N_train],train_pred)
acc2 = accuracy_score(digits.target[N_train:],test_pred)
print("训练集准确率:%f" % acc1)
print("测试集准确率:%f" % acc2)

# 数据错误可视化
y_pred = np.append(train_pred,test_pred)
range_num = int(np.shape(digits.target)[0])
error_index = [i for i in range(range_num) if digits.target[i] != y_pred[i]]
fig = plt.figure(figsize = (8,8))
fig.subplots_adjust(left = 0,right = 1,bottom = 0,top = 1,hspace = 0,wspace = 0)
# 显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
fig.suptitle('错误数据绘图，左下为正确数字，右下为模型计算结果')
n = 1
for i in error_index:
    ax=fig.add_subplot(math.ceil(len(error_index)/10)+1,10,n+10,xticks = [],yticks = [])
    n = n+1
    ax.imshow(digits.images[i],cmap = plt.cm.binary)
    ax.text(0,7,str(digits.target[i]))
    ax.text(6,7,str(int(y_pred[i])))
plt.show()
